#include <numeric>
#include <cmath>
#include "hexagon.hh"
#include "tools.hh"
#include "constant.hh"

namespace icehpc
{
    hexagon::hexagon(size_type div, value_type length)
        :
            div(div),
            width(4 * div + 1),
            height(2 * div + 1),
            n(width * height),
            nactive(6 * div * div + 4 * div + 1),
            except_id(div),
            length(length),
            delta(length / 2. / div)
    {
        auto sum = std::accumulate(f.begin(), f.end(), 0.);
        c = -constant::r3 * length * sum / 24.;
    }

    bool hexagon::boundary(size_type id) const
    {
        auto x = id % width;
        auto y = id / width;
        if (y == 0          && tools::inrange(x, div, 3 * div)) return true;
        if (y == height - 1 && tools::inrange(x, div, 3 * div)) return true;
        if (div - x     == y || div - x     == height - 1 - y) return true;
        if (x - 3 * div == y || x - 3 * div == height - 1 - y) return true;
        return false;
    }

    bool hexagon::inside(size_type id) const
    {
        auto x = id % width;
        auto y = id / width;
        if (y <= div) return tools::inrange(x, div - y + 1, 3 * div + y - 1);
        auto d = height - 1 - y;
        return tools::inrange(x, div - d + 1, 3 * div + d - 1);
    }

    bool hexagon::outside(size_type id) const
    {
        return !boundary(id) && !inside(id);
    }

    bool hexagon::top(size_type id) const
    {
        if (outside(id)) return false;
        auto y = id / width;
        return y == height - 1;
    }

    bool hexagon::bottom(size_type id) const
    {
        if (outside(id)) return false;
        auto y = id / width;
        return y == 0;
    }

    point hexagon::real_pos(size_type id) const
    {
        auto x = id % width;
        auto y = id / width;
        int cx{2 * div};
        int cy{div};
        int dx{x - cx};
        int dy{y - cy};
        return {dx * delta, dy * constant::r3 * delta};
    }

    auto hexagon::identity(size_type x, size_type y) const -> size_type
    {
        return y * width + x;
    }

    auto hexagon::up(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        return identity(x, y + 1);
    }

    auto hexagon::down(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        return identity(x, y - 1);
    }

    auto hexagon::left(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        return identity(x - 1, y);
    }

    auto hexagon::right(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        return identity(x + 1, y);
    }

    auto hexagon::x_non_empty_dir(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        auto dx = x > 2 * div ? +1 : -1;
        return identity(x - dx, y);
    }

    auto hexagon::y_non_empty_dir(size_type id) const -> size_type
    {
        auto x = id % width;
        auto y = id / width;
        auto dy = y > div ? +1 : -1;
        return identity(x, y - dy);
    }

    auto hexagon::value(size_type id) const -> value_type
    {
        auto p = real_pos(id);
        auto constexpr pi = constant::pi;
        if (p.x == 0 && p.y == 0) return f[0];
        double cos{p.x / std::hypot(p.x, p.y)};
        double angle{std::acos(cos)};
        if (p.y < 0) angle = 2. * pi - angle;
        int index = angle / (pi / 3.) + constant::eps;
        return f[index];
    }
}

